﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Text;

namespace Ooaavee.Support
{
    public static class Reflector
    {
        public static ReflectedObject GetReflectedObject(object component)
        {
            if (component == null)
            {
                throw new ArgumentNullException("component");
            }

            TypeCache cache = new TypeCache();

            ReflectedObject obj = new ReflectedObject();

            Type type = TypeDescriptor.GetReflectionType(component);

            // Type
            obj.Type = GetReflectedType(type, cache);

            // Value
            obj.Value = GetValue(null, component);

            // Public properties
            foreach (PropertyInfo info in cache.GetPublicProperties(type))
            {
                PropertyDescriptor descriptor = new PropertyDescriptorImpl(info);
                ReflectedProperty property = GetReflectedProperty(descriptor, component, cache); 
                obj.PublicProperties.Add(property);
            }

            // Non-public properties
            foreach (PropertyInfo info in cache.GetNonPublicProperties(type))
            {
                PropertyDescriptor descriptor = new PropertyDescriptorImpl(info);
                ReflectedProperty property = GetReflectedProperty(descriptor, component, cache);
                obj.NonPublicProperties.Add(property);
            }

            // Public fields
            foreach (FieldInfo info in cache.GetPublicFields(type))
            {
                PropertyDescriptor descriptor = new FieldDescriptorImpl(info);
                ReflectedField field = GetReflectedField(descriptor, component, cache);
                obj.PublicFields.Add(field);                
            }

            // Non-public fields
            foreach (FieldInfo info in cache.GetNonPublicFields(type))
            {
                PropertyDescriptor descriptor = new FieldDescriptorImpl(info);
                ReflectedField field = GetReflectedField(descriptor, component, cache);
                obj.NonPublicFields.Add(field);
            }

            return obj;
        }

        private static ReflectedProperty GetReflectedProperty(PropertyDescriptor componentDescriptor, object component, TypeCache cache)
        {
            ReflectedProperty reflected;

            if (cache.TryGetReflectedProperty(componentDescriptor, component, out reflected) == false)
            {
                reflected = new ReflectedProperty();

                // Name
                reflected.Name = componentDescriptor.Name;

                // Type
                reflected.Type = GetReflectedType(componentDescriptor.PropertyType, cache);

                // Value
                reflected.Value = GetValue(componentDescriptor, component);

                cache.Put(component, reflected);

                if (reflected.Value.Value != null)
                {
                    // Public properties
                    foreach (PropertyInfo info in cache.GetPublicProperties(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new PropertyDescriptorImpl(info);
                        ReflectedProperty property = GetReflectedProperty(descriptor, reflected.Value.Value, cache);
                        reflected.PublicProperties.Add(property);
                    }

                    // Non-public properties
                    foreach (PropertyInfo info in cache.GetNonPublicProperties(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new PropertyDescriptorImpl(info);
                        ReflectedProperty property = GetReflectedProperty(descriptor, reflected.Value.Value, cache);
                        reflected.NonPublicProperties.Add(property);
                    }

                    // Public fields
                    foreach (FieldInfo info in cache.GetPublicFields(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new FieldDescriptorImpl(info);
                        ReflectedField field = GetReflectedField(descriptor, reflected.Value.Value, cache);
                        reflected.PublicFields.Add(field);
                    }

                    // Non-public fields
                    foreach (FieldInfo info in cache.GetNonPublicFields(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new FieldDescriptorImpl(info);
                        ReflectedField field = GetReflectedField(descriptor, reflected.Value.Value, cache);
                        reflected.NonPublicFields.Add(field);
                    }
                }                
            }

            return reflected;
        }

        private static ReflectedField GetReflectedField(PropertyDescriptor componentDescriptor, object component, TypeCache cache)
        {
            ReflectedField reflected;

            if (cache.TryGetReflectedField(componentDescriptor, component, out reflected) == false)
            {
                reflected = new ReflectedField();

                // Name
                reflected.Name = componentDescriptor.Name;

                // Type
                reflected.Type = GetReflectedType(componentDescriptor.PropertyType, cache);

                // Value
                reflected.Value = GetValue(componentDescriptor, component);
                
                cache.Put(component, reflected);

                if (reflected.Value.Value != null)
                {
                    // Public properties
                    foreach (PropertyInfo info in cache.GetPublicProperties(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new PropertyDescriptorImpl(info);
                        ReflectedProperty property = GetReflectedProperty(descriptor, reflected.Value.Value, cache);
                        reflected.PublicProperties.Add(property);
                    }

                    // Non-public properties
                    foreach (PropertyInfo info in cache.GetNonPublicProperties(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new PropertyDescriptorImpl(info);
                        ReflectedProperty property = GetReflectedProperty(descriptor, reflected.Value.Value, cache);
                        reflected.NonPublicProperties.Add(property);
                    }

                    // Public fields
                    foreach (FieldInfo info in cache.GetPublicFields(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new FieldDescriptorImpl(info);
                        ReflectedField field = GetReflectedField(descriptor, reflected.Value.Value, cache);
                        reflected.PublicFields.Add(field);
                    }

                    // Non-public fields
                    foreach (FieldInfo info in cache.GetNonPublicFields(componentDescriptor.PropertyType))
                    {
                        PropertyDescriptor descriptor = new FieldDescriptorImpl(info);
                        ReflectedField field = GetReflectedField(descriptor, reflected.Value.Value, cache);
                        reflected.NonPublicFields.Add(field);
                    }

                    if (reflected.PublicProperties.Count == 0 && 
                        reflected.NonPublicProperties.Count == 0 &&
                        reflected.PublicFields.Count == 0 && 
                        reflected.NonPublicFields.Count == 1)
                    {
                        if (reflected == reflected.NonPublicFields[0])
                        {
                            reflected.NonPublicFields.Clear();
                        }
                    }
                }
            }

            return reflected;
        }

        private static ReflectedType GetReflectedType(Type type, TypeCache cache)
        {
            ReflectedType reflected;

            if (cache.TryGetReflectedType(type, out reflected) == false)
            {
                string genericTypeFullName = null;
                if (type.IsGenericType)
                {
                    TryGetGenericTypeFullName(type, out genericTypeFullName);
                }

                reflected = new ReflectedType();
                reflected.Name = type.Name;

                if (type.IsGenericType && string.IsNullOrEmpty(genericTypeFullName) == false)
                {
                    reflected.FullName = genericTypeFullName;
                }
                else
                {
                    reflected.FullName = type.FullName;                    
                }

                reflected.AssemblyQualifiedName = type.AssemblyQualifiedName;
                
                cache.Put(type, reflected);
            }

            return reflected;
        }

        /// <summary>
        /// Tries to get a generic type full name.
        /// </summary>
        /// <param name="type">The generic type.</param>
        /// <param name="name">The full name of the generic type.</param>
        /// <returns>true/false</returns>
        private static bool TryGetGenericTypeFullName(Type type, out string name)
        {
            name = null;

            string fullName = type.FullName;

            int indexOf = fullName.IndexOf('`');
            if (indexOf > 0)
            {
                StringBuilder s = new StringBuilder();

                s.Append(fullName.Substring(0, indexOf));

                s.Append("<");
                for (int i = 0; i < type.GenericTypeArguments.Length; i++)
                {
                    if (i > 0)
                    {
                        s.Append(", ");
                    }
                    s.Append(type.GenericTypeArguments[i].FullName);
                }
                s.Append(">");

                name = s.ToString();

                return true;
            }

            return false;
        }

        private static ReflectedValue GetValue(PropertyDescriptor componentDescriptor, object component)
        {
            ReflectedValue reflected = new ReflectedValue();

            try
            {
                if (componentDescriptor != null)
                {
                    reflected.Value = componentDescriptor.GetValue(component);
                }
                else
                {
                    reflected.Value = component;
                }
            }
            catch (Exception ex)
            {
                reflected.ErrorMessage = ex.Message;
            }

            if (reflected.Value != null)
            {
                int listCount;
                bool isList = TryGetIListData(reflected.Value, out listCount);

                if (isList)
                {
                    reflected.DisplayValue = string.Format("Count = {0}", listCount);
                }
                else
                {
                    try
                    {
                        reflected.DisplayValue = reflected.Value.ToString();
                    }
                    catch (Exception ex)
                    {
                        reflected.ErrorMessage = ex.Message;
                    }
                }
            }

            return reflected;
            //reflected.Type = GetReflectedType(componentDescriptor.PropertyType, cache);

            //object propertyValue = null;
            //try
            //{
            //    propertyValue = componentDescriptor.GetValue(component);
            //}
            //catch (Exception ex)
            //{
            //}

            //try
            //{
            //    propertyValue = componentDescriptor.GetValue(component);
            //}
            //catch (Exception ex)
            //{
            //}


            //if (propertyValue != null)
            //{
            //    try
            //    {
            //        reflected.Value = propertyValue.ToString();
            //    }
            //    catch (Exception ex)
            //    {
            //    }
            //}
        }

        private static bool TryGetIListData(object propertyValue, out int count)
        {
            count = 0;
            if (propertyValue != null)
            {
                IList list = propertyValue as IList;
                if (list != null)
                {
                    count = list.Count;
                    return true;
                }

            }
            return false;
        }


        [Serializable]
        public sealed class ReflectedObject 
        {
            private readonly Collection<ReflectedField> _publicFields = new Collection<ReflectedField>();
            private readonly Collection<ReflectedField> _nonPublicFields = new Collection<ReflectedField>();
            private readonly Collection<ReflectedProperty> _publicProperties = new Collection<ReflectedProperty>();
            private readonly Collection<ReflectedProperty> _nonPublicProperties = new Collection<ReflectedProperty>();

            public Collection<ReflectedField> PublicFields
            {
                get { return _publicFields; }
            }

            public Collection<ReflectedField> NonPublicFields
            {
                get { return _nonPublicFields; }
            }

            public Collection<ReflectedProperty> PublicProperties
            {
                get { return _publicProperties; }
            }

            public Collection<ReflectedProperty> NonPublicProperties
            {
                get { return _nonPublicProperties; }
            }

            public ReflectedType Type { set; get; }

            public ReflectedValue Value { set; get; }
        }

        [Serializable]
        public sealed class ReflectedField 
        {
            private readonly Collection<ReflectedField> _publicFields = new Collection<ReflectedField>();
            private readonly Collection<ReflectedField> _nonPublicFields = new Collection<ReflectedField>();
            private readonly Collection<ReflectedProperty> _publicProperties = new Collection<ReflectedProperty>();
            private readonly Collection<ReflectedProperty> _nonPublicProperties = new Collection<ReflectedProperty>();

            public string Name { set; get; }

            public ReflectedType Type { set; get; }

            public Collection<ReflectedField> PublicFields
            {
                get { return _publicFields; }
            }

            public Collection<ReflectedField> NonPublicFields
            {
                get { return _nonPublicFields; }
            }

            public Collection<ReflectedProperty> PublicProperties
            {
                get { return _publicProperties; }
            }

            public Collection<ReflectedProperty> NonPublicProperties
            {
                get { return _nonPublicProperties; }
            }

            public ReflectedValue Value { set; get; }
        }

        [Serializable]
        public sealed class ReflectedProperty 
        {
            private readonly Collection<ReflectedField> _publicFields = new Collection<ReflectedField>();
            private readonly Collection<ReflectedField> _nonPublicFields = new Collection<ReflectedField>();
            private readonly Collection<ReflectedProperty> _publicProperties = new Collection<ReflectedProperty>();
            private readonly Collection<ReflectedProperty> _nonPublicProperties = new Collection<ReflectedProperty>();

            public string Name { set; get; }

            public ReflectedType Type { set; get; }

            public Collection<ReflectedField> PublicFields
            {
                get { return _publicFields; }
            }

            public Collection<ReflectedField> NonPublicFields
            {
                get { return _nonPublicFields; }
            }

            public Collection<ReflectedProperty> PublicProperties
            {
                get { return _publicProperties; }
            }

            public Collection<ReflectedProperty> NonPublicProperties
            {
                get { return _nonPublicProperties; }
            }

            public ReflectedValue Value { set; get; }
        }

        [Serializable]
        public sealed class ReflectedType 
        {
            /// <summary>
            /// The name.
            /// </summary>
            public string Name { set; get; }

            /// <summary>
            /// The fully qualified name.
            /// </summary>
            public string FullName { set; get; }

            /// <summary>
            /// The assembly-qualified name.
            /// </summary>
            public string AssemblyQualifiedName { set; get; }
        }

        [Serializable]
        public sealed class ReflectedValue
        {
            [NonSerialized] private object _value;

            /// <summary>
            /// The display value.
            /// </summary>
            public string DisplayValue { set; get; }

            public string ErrorMessage { set; get; }

            internal object Value { set { _value = value; } get { return _value; } }
        }

        private class TypeCache
        {
            private readonly Dictionary<object, Dictionary<string, ReflectedProperty>> _reflectedProperties = new Dictionary<object, Dictionary<string, ReflectedProperty>>();
            private readonly Dictionary<object, Dictionary<string, ReflectedField>> _reflectedFields = new Dictionary<object, Dictionary<string, ReflectedField>>();
            private readonly Dictionary<Type, ReflectedType> _reflectedTypes = new Dictionary<Type, ReflectedType>();
            private readonly Dictionary<Type, FieldInfo[]> _nonPublicField = new Dictionary<Type, FieldInfo[]>();
            private readonly Dictionary<Type, FieldInfo[]> _publicFields = new Dictionary<Type, FieldInfo[]>();
            private readonly Dictionary<Type, PropertyInfo[]> _nonPublicProperties = new Dictionary<Type, PropertyInfo[]>();
            private readonly Dictionary<Type, PropertyInfo[]> _publicProperties = new Dictionary<Type, PropertyInfo[]>();
            
            public FieldInfo[] GetPublicFields(Type type)
            {
                FieldInfo[] value;
                if (_publicFields.TryGetValue(type, out value) == false)
                {
                    value = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
                    _publicFields[type] = value;
                }
                return value;
            }

            public FieldInfo[] GetNonPublicFields(Type type)
            {
                FieldInfo[] value;
                if (_nonPublicField.TryGetValue(type, out value) == false)
                {
                    value = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
                    _nonPublicField[type] = value;
                }
                return value;
            }

            public PropertyInfo[] GetPublicProperties(Type type)
            {
                PropertyInfo[] value;
                if (_publicProperties.TryGetValue(type, out value) == false)
                {
                    value = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                    _publicProperties[type] = value;
                }
                return value;
            }

            public PropertyInfo[] GetNonPublicProperties(Type type)
            {
                PropertyInfo[] value;
                if (_nonPublicProperties.TryGetValue(type, out value) == false)
                {
                    value = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic);
                    _nonPublicProperties[type] = value;
                }
                return value;
            }

            public bool TryGetReflectedProperty(PropertyDescriptor descriptor, object component, out ReflectedProperty property)
            {
                property = null;

                Dictionary<string, ReflectedProperty> properties;
                if (_reflectedProperties.TryGetValue(component, out properties))
                {
                    if (properties.TryGetValue(descriptor.Name, out property))
                    {
                        return true;
                    }
                }

                return false;
            }

            public bool TryGetReflectedField(PropertyDescriptor descriptor, object component, out ReflectedField field)
            {
                field = null;

                Dictionary<string, ReflectedField> fields;
                if (_reflectedFields.TryGetValue(component, out fields))
                {
                    if (fields.TryGetValue(descriptor.Name, out field))
                    {
                        return true;
                    }
                }

                return false;
            }

            public bool TryGetReflectedType(Type type, out ReflectedType reflected)
            {
                return _reflectedTypes.TryGetValue(type, out reflected);
            }

            public void Put(object component, ReflectedProperty property)
            {
                Dictionary<string, ReflectedProperty> items;
                if (_reflectedProperties.TryGetValue(component, out items) == false)
                {
                    items  = new Dictionary<string, ReflectedProperty>();
                    _reflectedProperties.Add(component, items);
                }
                items.Add(property.Name, property);
            }

            public void Put(object component, ReflectedField field)
            {
                Dictionary<string, ReflectedField> items;
                if (_reflectedFields.TryGetValue(component, out items) == false)
                {
                    items = new Dictionary<string, ReflectedField>();
                    _reflectedFields.Add(component, items);
                }
                items.Add(field.Name, field);
            }

            public void Put(Type type, ReflectedType reflected)
            {
                _reflectedTypes.Add(type, reflected);
            }
        }

        private sealed class PropertyDescriptorImpl : PropertyDescriptor
        {
            private readonly PropertyInfo _propertyInfo;

            public PropertyDescriptorImpl(PropertyInfo propertyInfo) : base(propertyInfo.Name, Array.ConvertAll(propertyInfo.GetCustomAttributes(true), o => (Attribute) o))
            {
                _propertyInfo = propertyInfo;
            }

            public override bool CanResetValue(object component)
            {
                return false;
            }

            public override Type ComponentType
            {
                get { return _propertyInfo.DeclaringType; }
            }

            public override object GetValue(object component)
            {
                return _propertyInfo.GetValue(component);
            }

            public override bool IsReadOnly
            {
                get { return !_propertyInfo.CanWrite; }
            }

            public override Type PropertyType
            {
                get { return _propertyInfo.PropertyType; }
            }

            public override void ResetValue(object component)
            {
            }

            public override void SetValue(object component, object value)
            {
                _propertyInfo.SetValue(component, value, null);
            }

            public override bool ShouldSerializeValue(object component)
            {
                return false;
            }
        }

        private sealed class FieldDescriptorImpl : PropertyDescriptor
        {
            private readonly FieldInfo _fieldInfo;

            public FieldDescriptorImpl(FieldInfo fieldInfo) : base(fieldInfo.Name, Array.ConvertAll(fieldInfo.GetCustomAttributes(true), o => (Attribute) o))
            {
                _fieldInfo = fieldInfo;
            }

            public override bool CanResetValue(object component)
            {
                return false;
            }

            public override Type ComponentType
            {
                get { return _fieldInfo.DeclaringType; }
            }

            public override object GetValue(object component)
            {
                return _fieldInfo.GetValue(component);
            }

            public override bool IsReadOnly
            {
                get { return false; }
            }

            public override Type PropertyType
            {
                get { return _fieldInfo.FieldType; }
            }

            public override void ResetValue(object component)
            {
            }

            public override void SetValue(object component, object value)
            {
                _fieldInfo.SetValue(component, value);
            }

            public override bool ShouldSerializeValue(object component)
            {
                return false;
            }
        }

    }
}